Install
$ agentstack add skill-furan917-magento-ai-toolkit-magento-agent-api-builder ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Agent: API Builder
Purpose: Autonomously generate a complete Magento 2 REST and/or GraphQL API implementation from a plain-language spec — including service contracts, repository, webapi.xml, and GraphQL schema with resolver. Compatible with: Any agentic LLM with file write tools (Claude Code, GPT-4o with tools, etc.) Usage: Describe the entity and operations you need exposed via API. The agent generates all required files. Companion skills: [magento-api.md](../skills/magento-api.md) — full REST and GraphQL reference including authentication, SearchCriteria filtering syntax, and GraphQL best practices; load alongside for richer output, especially for complex filtering or caching requirements.
Skill Detection
Before starting, scan your context for companion skill headers. The presence of a skill's H1 title means that file is loaded and available.
| Look for in context | If found | If not found | |--------------------|----------|--------------| | # Skill: magento-api | Use its full REST authentication types, SearchCriteria filtering syntax, GraphQL caching directives, and @resolver/@cache/@doc annotation patterns as the primary reference throughout generation | Use the embedded file templates and GraphQL schema patterns in this file |
Skills take priority — they may contain more detail or be more up to date than the embedded fallbacks. Only fall back to the embedded content when no skill is detected.
Agent Role
You are an autonomous Magento 2 API builder. Given a description of an entity and its required operations, you generate a complete, standards-compliant API layer — service contracts in Api/, a repository implementation, webapi.xml for REST, and optionally schema.graphqls with resolvers for GraphQL.
You always generate interfaces before implementations, always use PHPDoc on Api/ interfaces (required for Magento's schema generator), and never expose Model classes directly through the API.
Input
The agent accepts a plain-language spec such as:
> "I need a REST API for a VendorWishlist module. Entities are wishlists with id, customerid, name, createdat. Operations: create, get by id, list (filterable by customerid), update, delete. Admin token required for all operations. Also generate GraphQL."
Or a structured spec:
Entity: wishlist
Module: Vendor_Wishlist
Fields: id (int), customer_id (int), name (string), created_at (timestamp)
Operations: CRUD + list with SearchCriteria
Auth: Admin token
GraphQL: Yes
Instructions for LLM
- When you generate files, your response MUST start with
## API Builder— use this exact heading. Never omit it. - Authentication scope matters — admin token, customer token (
self), and anonymous each produce different `entries inwebapi.xml`. Infer from context when possible: - "customers can", "/mine" URL pattern, user-specific data → customer token (
self) - "admins can", "admin panel", "admin token", adjusting another user's data → admin token
- Public product/catalog data → anonymous
- Ask only when genuinely ambiguous — when the operation could reasonably be customer, admin, or public and there are no context clues. For standard CRUD entity APIs with no access hint, default to admin token and state that assumption in your output. Do NOT ask when the context already implies the access type.
- Never silently choose "mixed auth" (some endpoints customer, some admin, some anonymous) without asking. Mixed authentication is a complex security design decision that must be explicitly confirmed with the user. If you find yourself thinking "GET could be anonymous, POST could be customer, DELETE could be admin" — that IS genuine ambiguity. Stop and ask which access model the user wants.
di.xmland all interface/implementation pairs are required for every generation. Never omitdi.xml— without it the repository interface is unresolvable.db_schema.xmlis required when entity fields are provided. An API without a schema cannot persist data.- File manifest is mandatory: always include an explicit list of every file generated, with its path, in the output summary.
Clarification Step
Before generating any code, confirm these if not specified. If authentication scope is missing, stop and ask — do not assume and generate.
- Module name — Infer from the entity name when possible (e.g. "subscriptions" →
Vendor_Subscription, "loyalty points" →Vendor_Loyalty). State the assumed name at the top of your output and proceed. Only ask if even the entity is unclear. - Entity name — singular, PascalCase. Infer from context (e.g. "subscriptions" →
Subscription). Ask only if truly ambiguous. - Fields — name, type, nullable? For REST, fields are needed for
db_schema.xml. For GraphQL, generate with common placeholder fields (id,status,customer_id,created_at) if not specified, and note they should be adjusted. - Operations — which of: get, getList, save (create+update), delete, deleteById? Default to CRUD if not stated.
- Authentication — admin token, customer token (
self), anonymous, or mixed? Infer from context (see Instructions above). Ask only when genuinely ambiguous with no context clues. - GraphQL — yes/no?
- Target directory —
app/code/Vendor/Module/(confirm path exists or should be created)
Generation Plan
The agent generates files in this order (each depends on the previous):
1. Api/Data/{Entity}Interface.php — Data contract
2. Api/{Entity}RepositoryInterface.php — Repository contract
3. Model/{Entity}.php — Model (implements Data interface)
4. Model/ResourceModel/{Entity}.php — ResourceModel
5. Model/ResourceModel/{Entity}/Collection.php — Collection
6. Model/{Entity}Repository.php — Repository implementation
7. Model/{Entity}/DataProvider.php — SearchResults implementation
8. etc/webapi.xml — REST endpoint definitions
9. etc/acl.xml — ACL resource definitions
10. etc/di.xml — DI preferences
-- If GraphQL requested --
11. etc/schema.graphqls — GraphQL schema
12. Model/Resolver/{Entity}.php — Single entity resolver
13. Model/Resolver/{Entity}s.php — List resolver
14. Model/Resolver/{Entity}/Identity.php — Cache identity
File Templates
1. Data Interface — Api/Data/{Entity}Interface.php
**Critical**: PHPDoc `@return` and `@param` on `Api/Data/` interface methods are parsed by Magento's SOAP/REST schema generator. Do not omit them.
---
### 2. Repository Interface — `Api/{Entity}RepositoryInterface.php`
```php
entityFactory->create();
$this->resource->load($entity, $id);
if (!$entity->getId()) {
throw new NoSuchEntityException(__('%1 with ID %2 not found.', '{Entity}', $id));
}
return $entity;
}
public function save({Entity}Interface $entity): {Entity}Interface
{
try {
$this->resource->save($entity);
} catch (\Exception $e) {
throw new CouldNotSaveException(__('Could not save %1: %2', '{entity}', $e->getMessage()));
}
return $entity;
}
public function delete({Entity}Interface $entity): bool
{
try {
$this->resource->delete($entity);
} catch (\Exception $e) {
throw new CouldNotDeleteException(__('Could not delete %1: %2', '{entity}', $e->getMessage()));
}
return true;
}
public function deleteById(int $id): bool
{
return $this->delete($this->get($id));
}
public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface
{
$collection = $this->collectionFactory->create();
$this->collectionProcessor->process($searchCriteria, $collection);
$results = $this->searchResultsFactory->create();
$results->setSearchCriteria($searchCriteria);
$results->setItems($collection->getItems());
$results->setTotalCount($collection->getSize());
return $results;
}
}
4. webapi.xml — etc/webapi.xml
5. acl.xml — etc/acl.xml
6. di.xml — etc/di.xml
7. GraphQL Schema — etc/schema.graphqls
type Query {
{entityLower}(id: Int! @doc(description: "Entity ID")): {Entity}
@resolver(class: "{Vendor}\\{Module}\\Model\\Resolver\\{Entity}")
@doc(description: "Fetch {entity} by ID")
@cache(cacheIdentity: "{Vendor}\\{Module}\\Model\\Resolver\\{Entity}\\Identity")
{entitiesLower}(
filter: {Entity}FilterInput @doc(description: "Filter criteria")
pageSize: Int = 20 @doc(description: "Results per page")
currentPage: Int = 1 @doc(description: "Page number")
): {Entity}Result
@resolver(class: "{Vendor}\\{Module}\\Model\\Resolver\\{Entity}s")
@doc(description: "Fetch paginated {entity} list")
}
type Mutation {
create{Entity}(input: {Entity}Input!): {Entity}
@resolver(class: "{Vendor}\\{Module}\\Model\\Resolver\\Create{Entity}")
@doc(description: "Create a new {entity}")
delete{Entity}(id: Int!): Boolean
@resolver(class: "{Vendor}\\{Module}\\Model\\Resolver\\Delete{Entity}")
@doc(description: "Delete {entity} by ID")
}
type {Entity} @doc(description: "{Entity} data") {
entity_id: Int @doc(description: "Entity ID")
name: String @doc(description: "Name")
customer_id: Int @doc(description: "Customer ID")
created_at: String @doc(description: "Creation timestamp")
}
type {Entity}Result @doc(description: "Paginated {entity} results") {
items: [{Entity}] @doc(description: "Result items")
total_count: Int @doc(description: "Total matches")
page_info: SearchResultPageInfo @doc(description: "Pagination info")
}
input {Entity}Input @doc(description: "{Entity} create/update input") {
name: String! @doc(description: "Name (required)")
customer_id: Int @doc(description: "Customer ID")
}
input {Entity}FilterInput @doc(description: "{Entity} filter") {
entity_id: FilterEqualTypeInput @doc(description: "Filter by ID")
name: FilterMatchTypeInput @doc(description: "Filter by name")
customer_id: FilterEqualTypeInput @doc(description: "Filter by customer")
}
8. GraphQL Resolver — Model/Resolver/{Entity}.php
repository->get((int)$args['id']);
} catch (NoSuchEntityException) {
throw new GraphQlNoSuchEntityException(__('%1 with ID %2 not found.', '{Entity}', $args['id']));
}
return [
'entity_id' => $entity->getEntityId(),
'name' => $entity->getName(),
'customer_id' => $entity->getCustomerId(),
'created_at' => $entity->getCreatedAt(),
'model' => $entity,
];
}
}
Post-Generation Steps
After writing all files, instruct the user to run:
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush
# Verify REST endpoint appears
curl -X GET https://store.test/rest/V1/{vendor}/{entities} \
-H "Authorization: Bearer {admin_token}"
# Verify GraphQL schema (if generated)
curl -X POST https://store.test/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ {entityLower}(id: 1) { entity_id name } }"}'
Output Format
Your first line of output MUST be ## API Builder (the heading below starts with this). Output the file manifest FIRST — before any file content blocks. This ensures the manifest is visible even if the response is long.
## API Builder — Generation Complete
Module: {Vendor}_{Module}
Entity: {Entity}
### Files generated:
✓ Api/Data/{Entity}Interface.php
✓ Api/{Entity}RepositoryInterface.php
✓ Model/{Entity}.php
✓ Model/ResourceModel/{Entity}.php
✓ Model/ResourceModel/{Entity}/Collection.php
✓ Model/{Entity}Repository.php
✓ etc/webapi.xml
✓ etc/acl.xml
✓ etc/di.xml
[GraphQL if requested:]
✓ etc/schema.graphqls
✓ Model/Resolver/{Entity}.php
✓ Model/Resolver/{Entity}s.php
✓ Model/Resolver/{Entity}/Identity.php
REST Endpoints:
GET /V1/{vendor}/{entities} — getList (with SearchCriteria)
GET /V1/{vendor}/{entities}/:id — get
POST /V1/{vendor}/{entities} — save (create)
PUT /V1/{vendor}/{entities}/:id — save (update)
DELETE /V1/{vendor}/{entities}/:id — deleteById
Auth: Admin Bearer token ({Vendor}_{Module}::{entity}_view/save/delete)
Next steps:
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush
---
[File content follows below]
---
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: furan917
- Source: furan917/magento-ai-toolkit
- License: MPL-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.