# Oracle Fusion Cloud Financials — Configuration Guide

This guide is for **teams building on Apideck**. It covers what the connector can do, which values
your customer has to supply, and the behaviour you need to design around.

The Oracle-side procedures — creating the integration user, assigning roles, granting data access,
registering the OAuth application, and the Receivables configuration — are performed by your
customer's Oracle administrator. They are documented once, for them:

- **[Connection guide](consumer+connection)** — the end-to-end setup your customer follows.
- **[Receivables setup guide](consumer+receivables_setup)** — the extra Oracle configuration the `invoices` and `invoice-items` resources need.
- **[Greenfield GL setup guide](consumer+greenfield_gl_setup)** — for a tenant with no General Ledger yet.

Send your customer to those. This guide does not restate the procedures — where it names a role,
a privilege or a scheduled process, it is so you can recognise the cause when a customer reports
a symptom, and it points at the step in their guide that fixes it.

## What the connector serves

| Resource | Operations | Notes |
| --- | --- | --- |
| `journal-entries` | list, get, create, delete | Create is **asynchronous**; delete only while unposted; **no update** |
| `invoices` | list, get, create | **No update, no delete** — Oracle's own constraint, see below |
| `ledger-accounts` | list, get | Needs the `Get Enterprise Structures Using REST Service` privilege |
| `tracking-categories` | list, get | Needs that privilege **and** a chart of accounts with a tracking segment |
| `invoice-items` | list, get | Receivables standard memo lines; read-only in Oracle's API too |
| `company-info` | get | Reports the ledger the connection is scoped to |
| `subsidiaries` | list, get | Oracle legal entities; **tenant-wide, not scoped to the connection's ledger**. Oracle documents legal entities under the same enterprise-structures privilege as `ledger-accounts`, so expect it to need that grant too — not separately verified |

The connector is in **beta** for the Accounting API. List endpoints paginate by **offset**; the
Accounting API caps `limit` at **200** per request, as it does for every connector.

Each connection is scoped to a **single General Ledger**. To integrate more than one ledger, create
a separate connection per ledger.

## Connection settings

Every value below comes from your customer's Oracle tenant. The [connection
guide](consumer+connection) tells them where to find each one — the step references are to
that document.

| Setting | Required | Where its value comes from |
| --- | --- | --- |
| **Host** | yes | The subdomain of the Fusion Applications URL (`abcd1234` in `https://abcd1234.fa.us2.oraclecloud.com`) — Step 4 |
| **Data Center** | yes | The data-center segment of the same URL (`us2`) — Step 4 |
| **Client ID** | yes | The OAuth confidential application in OCI IAM / IDCS — Step 5 |
| **Client Secret** | yes | Same application; authenticates the token request — Step 5 |
| **Token URL** | yes | The tenant's IDCS / OCI IAM token endpoint. **A different host** from Host/Data Center — Step 5 |
| **Scope** | yes | Per-tenant; read off the application's authorized-resources list in the OCI console — Step 5 |
| **Ledger** | yes | Dropdown, populated by a live read once data access exists — Step 6 |
| **Data Access Set** | yes | Dropdown, same. Must grant **Read-Write** for journal creation — Step 6 |
| **Default Journal Source** | yes (if using `journal-entries`) | A **fixed dropdown**, currently `Manual` / `Accruals` — not free text. Must be a source the customer's GL accepts for import — Step 7 |
| **Default Journal Category** | yes (if using `journal-entries`) | A **fixed dropdown**, currently `Manual` / `Accrual` / `Adjustment` — Step 7 |
| **Tracking Segment Value Set Code** | yes (if using `tracking-categories`) | The value set behind the chart-of-accounts segment to expose as tracking categories — Step 7 |
| **Receivables Business Unit** | yes (if using `invoices`) | The business unit invoices are created in — Step 7 |
| **Receivables Transaction Source** | yes (if using `invoices`) | A manual Receivables transaction source — Step 7 |
| **Invoice Transaction Type** | no | The Receivables transaction type name applied to a standard invoice — `type: "standard"` or no `type` at all. Tenant-defined; Oracle seeds one called `Invoice`. Left blank, Oracle applies its own default — Step 7 |
| **Invoice Flexfield Context** | no | Context code of the transaction descriptive flexfield whose segments `custom_fields` are written to. Leave empty when the segments are global — Step 7 |

Two of these are silent-failure risks worth knowing about when you support a customer:

- **Receivables Business Unit** only affects creates. An invoice created under a business unit the
  customer's data-access grant does not cover is **invisible to the connection afterwards**, so keep
  the two aligned. Empty invoice lists come from the data-access grant, not from this setting.
- **Tracking Segment Value Set Code** has nothing to point at on a chart of accounts with no
  tracking segment, and a chart cannot be restructured once its ledger has posted journals.

## Authentication

OAuth 2.0 client credentials against the customer's IDCS / OCI IAM `token_url` host, which is a
different domain from the Fusion instance every other request uses. Oracle maps the token to the
Fusion user whose **User Name is exactly the Client ID** (Step 5). Reads and invoice creation work;
on some tenants Oracle's scheduler intermittently refuses these tokens, which surfaces as `503` on
journal-entry create (see below).

**Oracle authorizes API calls under that Fusion Applications user, not under the OAuth client**, so
a token request can succeed while every REST call returns `403`: the user is missing its roles, or
no user with the Client ID as its name exists.

A blanket `403` has two other causes worth ruling out with your customer, both covered by the
[connection guide](consumer+connection): their Fusion user may lack the **Access FSCM
Integration Rest Service** privilege, which General Accounting Manager carries but a least-privilege
custom role has to be given explicitly; or the scheduled processes that make any role change
effective may simply not have been run yet.

## What connection validation does and does not prove

Validation calls the Ledgers list endpoint. A green validation confirms Host, Data Center and
authentication.

It does **not** confirm:

- that the Data Access Set grants **Read-Write** — validation only performs a read, so a read-only
  grant surfaces later as a journal-import security failure;
- that the user can see **any data at all** — Oracle returns an empty list rather than an error for
  a principal with no data access, so validation is green either way.

If General Ledger resources work but invoice lists come back empty, the cause is almost always
missing **Business Unit** data access: Receivables visibility is secured by business unit, entirely
separately from the GL Data Access Set. A new grant is also not live for a REST caller until **both** of the
scheduled processes in the connection guide's Step 2.5 have run, in order — `Send Pending LDAP
Requests` first, then `Import User and Role Application Security Data`; in live verification a role
change reached REST reads within a minute of the first completing. And on the verification pod the
integration user also needed **Application Implementation Consultant** to see Receivables at all —
see the Receivables guide, Section I. Verify with an API read: a grant that looks correct in
**Users with Data Access** is not proof it is live for a REST caller.

## Journal-entry behaviour

Creating a journal entry is **asynchronous** — it submits the entry to Oracle's journal-import
process and returns immediately. The journal does not exist yet when the call returns.

- The `id` in the create response is a **submission receipt**, prefixed with `ess-group:` (e.g.
  `ess-group:238151661897615`) so your code cannot mistake it for a journal id. You can use it on
  `journalEntriesOne` and `journalEntriesDelete` straight away: while the import is still running
  they answer `404`, and once the journal exists they answer for that journal, whose own id is in
  the response. The receipt keeps working for as long as the journal exists.
- **A create that returns 2xx has been submitted.** Oracle answers a submission it will not accept
  with a success status and a request id of `-1`; the connector turns that into an error rather than
  handing back an id that refers to nothing. Oracle gives no reason, and no import request exists to
  inspect, so the connector checks one thing itself: whether Oracle's scheduler accepts the
  connection's credentials at that moment. A **`503`** means it does not — on some tenants the
  scheduler refuses a **client-credentials** token for stretches of up to about thirty minutes while
  every read keeps working; retry later without changing anything. A
  **`403`** means the scheduler accepts the credentials, which makes a privilege gap the most
  likely cause — but the check cannot tell that apart from a submission Oracle would not accept, so
  treat it as a lead rather than a verdict and retry the journal on its own before changing any
  roles. A **`502`** means the check itself did not answer; retry. Closed periods and bad account
  combinations are **not** reported here: those surface only after a successful submission, in the
  asynchronous import's own log.
- **The request can also be refused before Oracle is contacted at all.** A **`422`** means the
  connection is missing one of Ledger, Data Access Set, Default Journal Source or Default Journal
  Category, and names the one that is missing. A **`400`** means the payload broke one of the rules
  below.
- **Reads return at most 25 line items per journal**, and on a list no more than the request's own
  `limit` per journal, while the entry's own totals describe the whole journal. Oracle exposes no
  route to the remaining lines, so a journal with more lines cannot be read in full through this
  API. A response whose lines may be incomplete carries a `truncated_child_collection` entry in
  `meta.warnings`.
- To find the resulting journal, **list** journal entries once the import has had time to complete
  (seconds to several minutes) and use the listed entry's `id` for subsequent `journalEntriesOne`
  calls.
- Journals are always created **Unposted**. Posting is a separate step in Oracle and is not performed
  by this connector.
- **Update is not supported at any status** — Oracle provides no API route to edit journal content.
  To change an unposted journal, delete it and recreate it; correct a posted journal with a reversal
  in Oracle. **Delete** works only while the journal is unposted.
- Only the ledger's functional currency is supported. No multi-currency journal entries.
- **Filtering:** `filter[start_date]` and `filter[end_date]` select on the Oracle **posting date**,
  inclusive; unposted journals have no posting date and never match a date range. `filter[status]`
  matches Oracle's stored status exactly, **including its letter case** — a tenant can hold both
  cases of the same status, and a journal stored in the other case reads back with the same
  unified `status` but is silently absent from a list filtered on it. So `filter[status]=draft` is
  **not** a complete way to find unposted journals, including one saved without completion; omit
  the filter and check each entry's own `status` when completeness matters. **`filter[end_date]`
  must be exactly `YYYY-MM-DD`** — a timestamp-shaped value (the form `filter[updated_since]` takes)
  is refused with a `400`. `filter[status]` and
  `filter[updated_since]` are supported; `filter[scope]` and `filter[subsidiary_id]` are refused.
  Sorting works on `created_at` and `updated_at`.
- Account and period validation errors are **not returned by the create call** — they surface only in
  the asynchronous import, which this connector does not expose. Your customer can check the request
  under **Navigator → Tools → Scheduled Processes** in Oracle.
- On a journal line, send the account as `ledger_account.code` (the account combination, e.g.
  `01.000.1200`) — **never** `ledger_account.id`, which is Oracle's internal combination id and is
  not a segmented account code.

### What a journal create accepts

Oracle takes the whole entry as a single import that the connector assembles and balances, so the
payload rules are stricter than the unified schema alone suggests. Each of these is refused with a
`400` naming the field, rather than being accepted and silently dropped:

- **Amounts are always positive.** `line_items[].type` (`debit` / `credit`) carries the direction on
  its own, and a zero or negative `total_amount` is refused. **This differs from the unified
  schema's own field description**, which describes credits as negative — send the absolute amount.
- **At least two lines**, and they must balance to zero.
- **Every line needs `ledger_account.code`**, non-blank. It is the only part of `ledger_account`
  that is read.
- **`posted_at` must be an ISO 8601 calendar date** (`YYYY-MM-DD`, optionally with a time). A
  non-ISO value such as `09/15/2026` is refused rather than interpreted, because its meaning depends
  on the writer's locale and the guess would select a different accounting period. `posted_at`
  selects the accounting date and therefore the period; `accounting_period` on its own selects the
  period directly, in `Mon-YY` form; sending both with different meanings is refused.
- **No tax.** `tax_code`, `tax_type` and a line's `tax_amount`, `tax_rate` or `tax_type` are refused
  if present. (`tax_inclusive` is currently ignored rather than refused.)
- **`memo` holds 214 characters, each line's `description` 240, and `title` 87.** Longer is refused —
  Oracle would accept it and then discard the whole import silently, or keep the journal and trim
  the title. Emoji and other characters outside the Basic Multilingual Plane count as four each
  against these limits, because that is how Oracle stores them.
- **`pass_through` is not supported on this operation and is refused.** Anything added to the
  submission makes Oracle reject the entire import without saying why, so it is rejected up front
  instead of producing a create that quietly never becomes a journal.

Fields the unified schema accepts here and Oracle never stores: `status` (journals always import
Unposted), `number`, `display_id`, `company_id`, `subsidiary`, `journal_symbol`,
`tracking_categories`, `custom_fields`, `attachments`, `source_type`, `source_id`, and per line
`line_number`, `base_currency_amount`, `tracking_category`/`tracking_categories`, `customer`,
`supplier`, `employee`, `department_id`, `location_id` and `worktags`. Cost centres and departments
are segments of the account combination, so they are set through `ledger_account.code`. The
per-resource API reference carries the full list.

## Invoice behaviour

- **Create only.** Oracle will only ever create a **completed** invoice, and a completed invoice
  cannot be deleted and accepts almost no edits — not its amounts, dates or lines. Update and delete
  are therefore not supported; correct an invoice with a credit memo or an adjustment, which is what
  Oracle itself expects.
- **Reference the customer with `customer.display_id`** (the account number) on a create.
  `customer.id` carries Oracle's internal party id — it is what `filter[customer_id]` takes on reads,
  but Oracle **refuses it on a write**.
- **Line amounts must be self-consistent, unless the line carries a discount.** Oracle computes
  each line's amount from `quantity × unit_price` and ignores a `total_amount` sent alongside, so
  a request whose `total_amount` disagrees with that product is rejected rather than silently
  invoiced for the product. A discounted line is the exception, because Receivables has no
  line-discount field: there the disagreement IS the discount, so `total_amount` is honoured and
  absorbed into the unit price. A discount sent with only a `unit_price` is treated as one unit;
  sent with only a `quantity`, there is no price to reduce and the request is refused rather than
  invoiced at full price.
- **Line amounts are not rounded to the currency.** A `quantity` or `unit_price` derived from the
  other line values is kept at ten decimals, so 100 over 3 units reads back as a unit price of
  `33.3333333333`; Oracle rounds only the invoice totals to the currency. Send `quantity` and
  `unit_price` when the exact line amount matters.
- **`invoice_date` does not set the accounting date.** Oracle assigns that itself, to an open period.
  Forcing it would fail for any invoice dated into a closed period.
- **Reference an invoice item by name** in `item.code`. Receivables matches memo lines on their
  exact, **case-sensitive** name and rejects the item's `id`, so `line_items[].item.id` is not
  populated. Some seeded memo lines are reserved for other document types and are refused on a
  customer invoice; `Goods and Services` is the general-purpose one.
- **The list is not restricted to sales invoices** — credit and debit memos come back in the same
  list, distinguished by `type`. Oracle transaction types are tenant-defined names, so a custom type
  reads as `other`.
- **Filtering:** `filter[id_since]`, `filter[updated_since]`, `filter[created_since]`,
  `filter[number]` and `filter[customer_id]` are supported. `filter[ids]` is **not** — Oracle cannot
  match a list of ids on this resource. Sorting works on `created_at`, `updated_at` and `id`.
- **Not available:** `subsidiary` (an invoice carries only the legal entity's code, not the id the
  Subsidiaries resource is keyed by) and `billing_address` / `shipping_address` (only site codes
  exist behind them — no street, city or country). Both are readable with `raw=true`.
- **Tax figures follow the tenant's tax configuration.** `sub_total` and `total_tax` are assembled
  from the invoice's installments, with freight included in `sub_total` so that
  `sub_total` + `total_tax` reconciles to `total`. On a tenant with no tax rules configured,
  `total_tax` is `0`. `line_items[].tax_amount` and `tax_rate.rate` are returned by the
  single-invoice read only; the list carries the header `total_tax` and each line's
  `tax_rate.code`.

## Reference-resource behaviour

- **`ledger-accounts`** — `name`, `code` and `display_id` all carry the account combination (e.g.
  `01.000.1200`); `id` is Oracle's internal combination id and is the only value that addresses a
  single record. `filter[classification]` and `filter[status]` are supported. `filter[name]` is not:
  Oracle publishes no description for an account combination. Classifications Oracle does not model
  (`income`, `other_income`, `other_expense`, `costs_of_sales`, `other`) match nothing and return an
  empty list.
- **`tracking-categories`** — a connection exposes **exactly one** chart-of-accounts segment, chosen
  by the Tracking Segment Value Set Code setting, so that ids stay unambiguous. Reads only.
- **`invoice-items`** — Receivables standard memo lines. Read-only in Oracle's API as well as here;
  they are maintained under Manage Standard Memo Lines. Inventory items from the Product Management
  item master are not returned. No filters and no sort — the list pages like every other list here.
- **`subsidiaries`** — Oracle legal entities, **tenant-wide**: the list is not scoped to the
  connection's ledger, so expect entities unrelated to it. Reads only.

## Make your first API call

```bash
curl --location 'https://unify.apideck.com/accounting/journal-entries' \
 --header 'x-apideck-consumer-id: test-consumer' \
 --header 'x-apideck-app-id: {APIDECK_APP_ID}' \
 --header 'x-apideck-service-id: oracle-fusion' \
 --header 'Authorization: Bearer {APIDECK_API_KEY}'
```
